module et {
	const objectSystems: { SysytemTarget: any, EntityTarget: any }[] = [];
	export function objectSystem<T extends Entity>(entityTarget: new () => T) {
		return function (target: any) {
			objectSystems.push({ SysytemTarget: target.prototype, EntityTarget: entityTarget.prototype });
		}
	}
	const eventHandles: { type: any, handle: any }[] = [];
	export function event(eventType: new () => any) {
		return function (target) {
			let eventHandle = { type: eventType.prototype, handle: target.prototype };
			eventHandles.push(eventHandle);
		}
	}
	const recveventHandles: { type: any, handle: any }[] = [];
	export function recvevent(eventType: new () => any) {
		return function (target) {
			let recveventHandle = { type: eventType.prototype, handle: target.prototype };
			recveventHandles.push(recveventHandle);
		}
	}
	class EventType {
		public Arg: any;
		public Properties: string[];
	}
	export class EventSystem {
		private static _Instance: EventSystem;
		public static get Instance() {
			if (!this._Instance) {
				this._Instance = new EventSystem();
				this._Instance.init();
			}
			return this._Instance;
		}
		private readonly eventTypePool = new Map<any, APool<EventType>>();
		private readonly allComponents: Map<string, Entity> = new Map<string, Entity>();
		//private readonly attributeTypeMap: UnOrderMultiMapSet<Function, { new() }> = new UnOrderMultiMapSet<Function, { new() }>();
		private readonly allEventHandles: Map<new () => any, Array<IEvent>> = new Map<new () => any, Array<IEvent>>();
		private readonly allRecvEventHandles: Map<new () => any, Array<IEvent>> = new Map<new () => any, Array<IEvent>>();
		private readonly awakeSystems = new UnOrderMultiMap<any, IAwakeSystem>();
		private readonly startSystems = new UnOrderMultiMap<any, IStartSystem>();
		private readonly updateSystems = new UnOrderMultiMap<any, IUpdateSystem>();
		private readonly fixedUpdateSystems = new UnOrderMultiMap<any, IFixedUpdateSystem>();
		private readonly lateUpdateSystems = new UnOrderMultiMap<any, ILateUpdateSystem>();
		private readonly destroySystems = new UnOrderMultiMap<any, IDestroySystem>();
		private readonly registSystems = new UnOrderMultiMap<any, IRegistSystem>();
		private readonly deserializeSystems = new UnOrderMultiMap<any, IDeserializeSystem>();


		private startEntitys = new Set<Entity>();
		private updateEntitys = new Set<Entity>();
		private lateUpdateEntitys = new Set<Entity>();
		private fixedUpdateEntitys = new Set<Entity>();
		public init(): void {
			this.awakeSystems.clear();
			this.startSystems.clear();
			this.lateUpdateSystems.clear();
			this.updateSystems.clear();
			this.destroySystems.clear();
			for (const objSystemType of objectSystems) {
				let objSystem = new objSystemType.SysytemTarget.constructor();
				if (objSystem instanceof IAwakeSystem) {
					this.awakeSystems.add(objSystemType.EntityTarget, objSystem);
				}
				else if (objSystem instanceof IStartSystem) {
					this.startSystems.add(objSystemType.EntityTarget, objSystem);
				}
				else if (objSystem instanceof IUpdateSystem) {
					this.updateSystems.add(objSystemType.EntityTarget, objSystem);
				}
				else if (objSystem instanceof ILateUpdateSystem) {
					this.lateUpdateSystems.add(objSystemType.EntityTarget, objSystem);
				}
				else if (objSystem instanceof IFixedUpdateSystem) {
					this.fixedUpdateSystems.add(objSystemType.EntityTarget, objSystem);
				}
				else if (objSystem instanceof IDestroySystem) {
					this.destroySystems.add(objSystemType.EntityTarget, objSystem);
				}
				else if (objSystem instanceof IRegistSystem) {
					this.registSystems.add(objSystemType.EntityTarget, objSystem)
				}
				else if (objSystem instanceof IDeserializeSystem) {
					this.deserializeSystems.add(objSystemType.EntityTarget, objSystem)
				}
			}
			this.allEventHandles.clear();
			for (const eventHandle of eventHandles) {
				this.registerEventHandle(eventHandle.type, new eventHandle.handle.constructor());
			}
			this.allRecvEventHandles.clear();
			for (const eventHandle of recveventHandles) {
				this.registerRecvEventHandle(eventHandle.type, new eventHandle.handle.constructor());
			}
		}
		public async publish(argType: new () => any, ...valueArgs: any[]) {
			let pool = this.eventTypePool.get(argType.prototype);
			if (!pool) return;
			let eventType = pool.fetch(EventType);
			if (!eventType.Arg) {
				eventType.Arg = new argType();
				eventType.Properties = Object.getOwnPropertyNames(eventType.Arg);
			}
			eventType.Properties.forEach((property, index) => {
				eventType.Arg[property] = valueArgs[index];
			});
			let handArr = this.allEventHandles.get(argType.prototype);
			if (handArr) {
				for (let index = 0; index < handArr.length; index++) {
					await handArr[index].handle(eventType.Arg);
				}
				pool.recycle(eventType);
			}
		}
		public async receive<T>(argType: new () => T) {
			let handArr = this.allEventHandles.get(argType.prototype);
			if (handArr && handArr.length > 0) {
				return await handArr[0].handle() as T;
			}
		}
		public registerEventHandle(type: any, ievent: IEvent) {
			let events = this.allEventHandles.get(type);
			if (events == null) {
				events = new Array<IEvent>();
				this.allEventHandles.set(type, events);
			}
			events.Add(ievent);

			if (!this.eventTypePool.has(type)) {
				let eventType = new EventType();
				eventType.Arg = new type.constructor();
				eventType.Properties = Object.getOwnPropertyNames(eventType.Arg);
				let apool = new APool<EventType>(); apool.recycle(eventType);
				this.eventTypePool.set(type, apool);
			}
		}
		public removeEventHandle(type: any, ievent: IEvent) {
			let handArr = this.allEventHandles.get(type);
			if (handArr) {
				for (let index = handArr.length - 1; index >= 0; index--) {
					if (handArr[index] == ievent) {
						handArr.RemoveAt(index);
						break;
					}
				}
			}
			if (this.eventTypePool.has(type)) {
				this.eventTypePool.delete(type);
			}
		}
		public registerRecvEventHandle(type: any, ievent: IEvent) {
			let events = this.allRecvEventHandles.get(type);
			if (events == null) {
				events = new Array<IEvent>();
				this.allRecvEventHandles.set(type, events);
			}
			events.Add(ievent);
		}
		public removeRecvEventHandle(type: any, ievent: IEvent) {
			let handArr = this.allRecvEventHandles.get(type);
			if (handArr) {
				for (let index = handArr.length - 1; index >= 0; index--) {
					if (handArr[index] == ievent) {
						handArr.RemoveAt(index);
						break;
					}
				}
			}
		}
		public registSystem(component: Entity, isRegister: boolean = true) {
			if (isRegister) {
				this.addSystem(component);
			}
			else {
				this.removeSystem(component);
			};
		}
		public addSystem(component: Entity) {
			this.allComponents.set(component.instanceId.toString(), component);
			let type = component.getType();
			if (this.startSystems.containsKey(type)) {
				this.startEntitys.add(component);
			}
			if (this.updateSystems.containsKey(type)) {
				this.updateEntitys.add(component);
			}
			if (this.lateUpdateSystems.containsKey(type)) {
				this.lateUpdateEntitys.add(component);
			}
			if (this.fixedUpdateSystems.containsKey(type)) {
				this.fixedUpdateEntitys.add(component);
			}

			if (this.registSystems.containsKey(type)) {
				let systems = this.registSystems.get(type);
				for (const system of systems) {
					system.run(component, true);
				}
			}
		}
		public removeSystem(component: Entity): void {
			this.allComponents.delete(component.instanceId.toString());
			this.startEntitys.delete(component);
			this.updateEntitys.delete(component);
			this.lateUpdateEntitys.delete(component);
			this.fixedUpdateEntitys.delete(component);

			let type = component.getType();
			if (this.registSystems.containsKey(type)) {
				let systems = this.registSystems.get(type);
				for (const system of systems) {
					system.run(component, false);
				}
			}
		}

		public get(instanceId: Long): Entity {
			let component = this.allComponents.get(instanceId.toString());
			return component;
		}

		public isRegister(instanceId: number) {
			return this.allComponents.has(instanceId.toString());
		}
		public awake<A, B, C>(component: Entity, a?: A, b?: B, c?: C): void {
			let systems = this.awakeSystems.get(component.getType());
			if (!systems) return;
			for (const system of systems) {
				if (system instanceof IAwakeSystem1) {
					system.run(component);
				}
				else if (system instanceof IAwakeSystem2) {
					system.run(component, a);
				}
				else if (system instanceof IAwakeSystem3) {
					system.run(component, a, b);
				}
				else if (system instanceof IAwakeSystem4) {
					system.run(component, a, b, c);
				}
			}
		}
		public start(): void {
			for (const entity of this.startEntitys) {
				let systems = this.startSystems.get(entity.getType());
				for (const system of systems) {
					system.run(entity);
				}
			}
			this.startEntitys.clear();
		}
		public update(): void {
			this.start();
			for (const entity of this.updateEntitys) {
				let systems = this.updateSystems.get(entity.getType());
				if (!systems) continue;
				for (const system of systems) {
					system.run(entity);
				}
			}
		}
		public lateUpdate(): void {
			for (const entity of this.lateUpdateEntitys) {
				let systems = this.lateUpdateSystems.get(entity.getType());
				if (!systems) continue;
				for (const system of systems) {
					system.run(entity);
				}
			}
		}
		public fixedUpdate(): void {
			for (const entity of this.fixedUpdateEntitys) {
				let systems = this.fixedUpdateSystems.get(entity.getType());
				if (!systems) continue;
				for (const system of systems) {
					system.run(entity);
				}
			}
		}
		public destroy(component: Entity) {
			let systems = this.destroySystems.get(component.getType());
			if (!systems) return;
			for (const system of systems) {
				system.run(component);
			}
		}
		public deserialize(component: Entity) {
			let systems = this.deserializeSystems.get(component.getType());
			if (!systems) return;
			for (const system of systems) {
				system.run(component);
			}
		}
	}
}